package com.deyuanyun.pic.settings.task.util;

import com.deyuanyun.pic.settings.task.SchedulerJob;
import com.deyuanyun.pic.settings.task.quartz.CalendarIntervalScheduleBuilder;
import org.joda.time.DateTime;
import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronScheduleBuilder;
import org.quartz.CronTrigger;
import org.quartz.DateBuilder;
import org.quartz.JobBuilder;
import org.quartz.JobDataMap;
import org.quartz.JobDetail;
import org.quartz.JobKey;
import org.quartz.ScheduleBuilder;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleScheduleBuilder;
import org.quartz.Trigger;
import org.quartz.TriggerBuilder;
import org.quartz.TriggerKey;
import org.quartz.impl.matchers.GroupMatcher;
import org.springframework.util.StringUtils;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * 定时任务管理类
 *
 * @author axiao(xj08.dream@gmail.com)
 * @version 1.0
 *          Histroy: [修改人][时间][描述]
 * @date 2016-07-11
 */
public class QuartzManagerUtil {

    public static final String DEFAULT_JOB_GROUP_NAME = "defaultJobGroup";//默认的任务组名

    private static Scheduler scheduler;

    /**
     * 初始化Scheduler
     * @param scheduler
     */
    public static void initStaticField(Scheduler scheduler) {
        QuartzManagerUtil.scheduler = scheduler;
    }

    /**
     * 获取Scheduler,<b>注意：如果在web上下文启动的时候要做任务的相关处理的时候一定要注意scheduler有可能没初始化的情况</b>
     * @return
     */
    public static Scheduler getScheduler() {
        return scheduler;
    }


    /**
     * 构建日历任务, 本方法会抛出异常，一定要主要必需参数的传递
     * @param schedulerJob
     */
    public static void addCalendarJob(SchedulerJob schedulerJob) {
        String name = schedulerJob.getName();
        String group = schedulerJob.getGroup();
        Date startTime = schedulerJob.getStartTime();
        Class cls = schedulerJob.getClazz();
        Map<String, Object> dataMap = schedulerJob.getDataMap();
        Date endTime = schedulerJob.getEndTime();
        int timeInterval = schedulerJob.getTimeInterval();
        DateBuilder.IntervalUnit unit = schedulerJob.getIntervalUnit();
        Integer endCount = schedulerJob.getEndCount();

        if(StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Job name cannot be empty.");
        }
        if(cls == null) {
            throw new IllegalArgumentException("Job class must be specified.");
        }
        if(unit == null) {
            throw new IllegalArgumentException("TimeUnit must be specified.");
        }
        if(timeInterval <= 0) {
            throw new IllegalArgumentException("Interval must be a positive value.");
        }
        if(startTime == null) {
            throw new IllegalArgumentException("\"startTime\" cannot be empty.");
        }

        if (StringUtils.isEmpty(group)) {
            group = DEFAULT_JOB_GROUP_NAME;
        }

        //DateTime nowDate = DateTime.now();
        DateTime startDateJoda = new DateTime(startTime);
        CalendarIntervalScheduleBuilder scheduleBuilder = CalendarIntervalScheduleBuilder.calendarIntervalSchedule()
                .withInterval(timeInterval, unit).setFirstTimeDay(startDateJoda.getDayOfMonth());

        /*if(startTime.compareTo(nowDate.toDate()) < 0) { //根据startTime算出任务的开始时间，这个开始时间在当前时间的过去不算
            while(true) {
                if(startDateJoda.compareTo(nowDate) >= 0) {
                    startTime = startDateJoda.toDate();
                    break;
                }
                if(unit.equals(DateBuilder.IntervalUnit.YEAR)) {
                    startDateJoda = startDateJoda.plusYears(timeInterval);
                } else if(unit.equals(DateBuilder.IntervalUnit.WEEK)) {
                    startDateJoda = startDateJoda.plusWeeks(timeInterval);
                } else if(unit.equals(DateBuilder.IntervalUnit.MONTH)) {
                    startDateJoda = startDateJoda.plusMonths(timeInterval);
                }else if(unit.equals(DateBuilder.IntervalUnit.DAY)) {
                    startDateJoda = startDateJoda.plusDays(timeInterval);
                } else {
                    //其它情况加小时
                    startDateJoda = startDateJoda.plusHours(timeInterval);
                }
            }
        }*/

        if(endCount != null && endCount != 0 && endTime == null) { //根据结束次数算出任务的结束时间
            int count = 0;
            DateTime d = new DateTime(startTime);
            while(true) {
                if(++count == endCount) {
                    endTime = d.toDate();
                    break;
                }
                if (unit.equals(DateBuilder.IntervalUnit.YEAR)) {
                    d =  d.plusYears(timeInterval);
                } else if (unit.equals(DateBuilder.IntervalUnit.WEEK)) {
                    d = d.plusWeeks(timeInterval);
                } else if (unit.equals(DateBuilder.IntervalUnit.MONTH)) {
                    d = d.plusMonths(timeInterval);
                    int monthMaxDay = d.dayOfMonth().getMaximumValue();
                    d = d.withDayOfMonth(monthMaxDay);
                } else if (unit.equals(DateBuilder.IntervalUnit.DAY)) {
                    d = d.plusDays(timeInterval);
                } else {
                    //其它情况加小时
                    d = d.plusHours(timeInterval);
                }
            }
        }
        if(endTime != null) {//在endTime不为空的情况下赋值结束时间到当天的23点59分59秒
            endTime = new DateTime(endTime).withTime(23, 59,59,0).toDate();
        }
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            CalendarIntervalTrigger trigger = (CalendarIntervalTrigger)scheduler.getTrigger(triggerKey);
            if(trigger == null) { //不存在创建任务
                JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(name, group).requestRecovery().build();

                if(dataMap != null && !dataMap.isEmpty())
                    jobDetail.getJobDataMap().putAll(dataMap); //添加任务需要的数据

                TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger().withIdentity(name, group)
                        .withSchedule(scheduleBuilder).startAt(startTime).endAt(endTime);

                if(dataMap != null && !dataMap.isEmpty()) { //添加任务需要的数据，JobDetail和Trigger所需的数据保持一致
                    triggerBuilder.usingJobData(new JobDataMap(dataMap));
                }
                trigger = (CalendarIntervalTrigger)triggerBuilder.build();
                scheduler.scheduleJob(jobDetail,  trigger);
            } else { //存在更新任务
                JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(name, group));

                updateJobDataMap(jobDetail, dataMap, true);

                // 重新构建trigger
                TriggerBuilder triggerBuilder = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).startAt(startTime).endAt(endTime);

                if(dataMap != null && !dataMap.isEmpty()) { //添加任务需要的数据，JobDetail和Trigger所需的数据保持一致
                    triggerBuilder.usingJobData(new JobDataMap(dataMap));
                }
                trigger = (CalendarIntervalTrigger)triggerBuilder.build();
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 构建一次性运行任务
     * @param schedulerJob
     */
    public static void addJobStartByDate(SchedulerJob schedulerJob){
        Date startTime = schedulerJob.getStartTime();
        Date d = new Date();
        if(startTime == null) { //判断执行时间是否为空
            schedulerJob.setStartTime(d);
        } else { //不为空的时候
            int compareInt = startTime.compareTo(d);
            if(compareInt <= 0) { //当当前时间
                schedulerJob.setStartTime(d);
            }
        }
        SimpleScheduleBuilder scheduleBuilder = SimpleScheduleBuilder.simpleSchedule();
        jobCreateOrUpdate(schedulerJob, scheduleBuilder);
    }


    /**
     * 添加一个cron构建的定时任务，cron表达式不能为空
     *
     * @param schedulerJob
     */
    public static void addCronJob(SchedulerJob schedulerJob) {
        String cronExpression = schedulerJob.getCron();
        if(StringUtils.isEmpty(cronExpression)) {
            throw new RuntimeException("CronExpression cannot be empty.");
        }
        CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
        jobCreateOrUpdate(schedulerJob, scheduleBuilder);
    }

    /**
     * 构建或更新任务
     * @param schedulerJob
     * @param scheduleBuilder
     */
    private static void jobCreateOrUpdate(SchedulerJob schedulerJob, ScheduleBuilder scheduleBuilder) {
        String name = schedulerJob.getName();
        String group = schedulerJob.getGroup();
        Class cls = schedulerJob.getClazz();
        Date endTime = schedulerJob.getEndTime();
        Map dataMap = schedulerJob.getDataMap();
        Date startTime = schedulerJob.getStartTime();

        if(StringUtils.isEmpty(name)) {
            throw new IllegalArgumentException("Job name cannot be empty.");
        }
        if(cls == null) {
            throw new IllegalArgumentException("Job class must be specified.");
        }
        if (StringUtils.isEmpty(group)) {
            group = DEFAULT_JOB_GROUP_NAME;
        }

        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            Trigger trigger = scheduler.getTrigger(triggerKey);

            // 不存在，创建一个
            if (trigger == null) {
                JobDetail jobDetail = JobBuilder.newJob(cls).withIdentity(name, group).requestRecovery().build();

                updateJobDataMap(jobDetail, dataMap, false);

                TriggerBuilder triggerBuilder = TriggerBuilder.newTrigger().withIdentity(name, group).withSchedule(scheduleBuilder).endAt(endTime);
                if(startTime != null) {
                    triggerBuilder.startAt(startTime);
                }
                trigger =  triggerBuilder.build();
                scheduler.scheduleJob(jobDetail, trigger);
            } else { // Trigger已存在，那么更新相应的定时设置
                JobDetail jobDetail = scheduler.getJobDetail(JobKey.jobKey(name, group));

                updateJobDataMap(jobDetail, dataMap, true);

                // 按新的cronExpression表达式重新构建trigger
                TriggerBuilder triggerBuilder = trigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).endAt(endTime);
                if(startTime != null) {
                    triggerBuilder.startAt(startTime);
                }
                trigger = triggerBuilder.build();
                // 按新的trigger重新设置job执行
                scheduler.rescheduleJob(triggerKey, trigger);
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 更新jobDataMap
     * @param jobDetail
     * @param newJobDataMap
     * @param isUpdate 是否需要执行addJob方法
     */
    private static void updateJobDataMap(JobDetail jobDetail, Map newJobDataMap, boolean isUpdate) {
        try {
            if(jobDetail != null && newJobDataMap != null) {
                JobDataMap jobDataMap = jobDetail.getJobDataMap();
                jobDataMap.putAll(newJobDataMap);//放入现在的设置的
                if(isUpdate)
                    scheduler.addJob(jobDetail, true, true);//重新设置保存JobDetail中的JobDataMap数据
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * 修改一个任务的触发时间
     *
     * @param name           触发器名
     * @param group          触发器组名
     * @param cronExpression cron表达式
     */
    public static void modifyJobTime(String name, String group, String cronExpression) {
        try {
            if (StringUtils.isEmpty(group)) {
                group = DEFAULT_JOB_GROUP_NAME;
            }
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            CronTrigger cronTrigger = (CronTrigger) scheduler.getTrigger(triggerKey);

            // 表达式调度构建器
            CronScheduleBuilder scheduleBuilder = CronScheduleBuilder.cronSchedule(cronExpression);
            // 按新的cronExpression表达式重新构建trigger
            cronTrigger = cronTrigger.getTriggerBuilder().withIdentity(triggerKey).withSchedule(scheduleBuilder).build();
            // 按新的trigger重新设置job执行
            scheduler.rescheduleJob(triggerKey, cronTrigger);
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 根据任务名和组名，获取触发器
     * @param name 任务名
     * @param group 组名
     */
    public static Trigger getTrigger(String name, String group) {
        try {
            TriggerKey triggerKey = TriggerKey.triggerKey(name, group);
            return scheduler.getTrigger(triggerKey);
        } catch (Exception e) {
            return null;
        }
    }

    /**
     * 根据任务组名获取到该组名下的所有任务的触发器
     * @param groupName 任务组名
     * @return
     */
    public static List<Trigger> getTriggers(String groupName){
        GroupMatcher groupMatcher = GroupMatcher.groupEndsWith(groupName);
        List<Trigger> triggers = new ArrayList<Trigger>();
        try {

            Set<TriggerKey> triggerKeys = scheduler.getTriggerKeys(groupMatcher);

            for (TriggerKey triggerKey : triggerKeys) {
                Trigger trigger =  scheduler.getTrigger(triggerKey);
                if(trigger != null) {
                    triggers.add(trigger);
                }
            }
        } catch (Exception e) {
            return null;
        }
        return triggers;
    }

    /**
     * 修改一个任务的触发时间(使用默认的任务组名)
     *
     * @param jobName        任务名
     * @param cronExpression cron表达式
     */
    public static void modifyJobTime(String jobName, String cronExpression) {
        modifyJobTime(jobName, null, cronExpression);
    }


    /**
     * 立即运行多个任务
     * @param jobKeys
     */
    public static void startJobNow(List<JobKey> jobKeys) {
        try {
            for (JobKey jobKey : jobKeys) {
                scheduler.triggerJob(jobKey);
            }
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * 立即运行单个任务
     * @param jobKey
     */
    public static void startJobNow(JobKey jobKey) {
        try {
            scheduler.triggerJob(jobKey);
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 立即运行单个任务
     * @param name 任务名
     * @param group 组名
     */
    public static void startJobNow(String name, String group) {
        startJobNow(JobKey.jobKey(name, group));
    }


    /**
     * 暂停任务
     * @param name 任务名
     * @param group 组名
     */
    public static void pauseJob(String name, String group) {
        try {
            JobKey jobKey = JobKey.jobKey(name, group);
            scheduler.pauseJob(jobKey);
        }catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * 恢复任务
     * @param name 任务名
     * @param group 组名
     */
    public static void resumeJob(String name, String group) {
        try {
            JobKey jobKey = JobKey.jobKey(name, group);
            scheduler.resumeJob(jobKey);
        }catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * 移除一个任务和触发器(使用默认的任务组名)
     *
     * @param name 任务名
     */

    public static void removeJob(String name) {
        String group = DEFAULT_JOB_GROUP_NAME;
        removeJob(name, group);
    }


    /**
     * 移除一个任务和触发器
     *
     * @param name  任务名
     * @param group 任务组名
     */
    public static void removeJob(String name, String group) {
        JobKey jobKey = JobKey.jobKey(name, group);
        removeJob(jobKey);
    }

    /**
     * 删除相关的job任务
     *
     * @param jobKey
     * @return
     */
    public static void removeJob(JobKey jobKey) {
        if(jobKey == null) return;
        try {
            scheduler.deleteJob(jobKey); //删除任务后，所对应的trigger也将被删除
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 得到所有的任务
     * @return
     */
    public static List<SchedulerJob> getAllJob(){
        try {
            GroupMatcher<JobKey> matcher = GroupMatcher.anyGroup();
            Set<JobKey> jobKeys = scheduler.getJobKeys(matcher);
            List<SchedulerJob> jobList = new ArrayList<SchedulerJob>();
            Set<JobKey> jobKeySet = scheduler.getJobKeys(matcher);
            for (JobKey jobKey : jobKeySet) {
                List<? extends Trigger> triggers = scheduler.getTriggersOfJob(jobKey);
                for (Trigger trigger : triggers) {
                    SchedulerJob jobVO = new SchedulerJob();
                    jobVO.setName(jobKey.getName());
                    jobVO.setGroup(jobKey.getGroup());
                    jobVO.setDesc("触发器：" + trigger.getKey());
                    jobVO.setNextTime(trigger.getNextFireTime());
                    jobVO.setPrevTime(trigger.getPreviousFireTime());
                    jobVO.setEndTime(trigger.getEndTime());
                    Trigger.TriggerState triggerState = scheduler.getTriggerState(trigger.getKey());
                    jobVO.setJobState(triggerState.name());
                    if(trigger instanceof CronTrigger) {
                        CronTrigger cronTrigger = (CronTrigger) trigger;
                        jobVO.setCron(cronTrigger.getCronExpression());
                        jobVO.setTimeZone(cronTrigger.getTimeZone().getID());
                    }
                    if(trigger instanceof CalendarIntervalTrigger) {
                        CalendarIntervalTrigger intervalTrigger = (CalendarIntervalTrigger) trigger;
                        jobVO.setTimeZone(intervalTrigger.getTimeZone().getID());
                    }
                    jobList.add(jobVO);
                }
            }
            return jobList;
        } catch (SchedulerException e) {
            throw new RuntimeException(e.getMessage());
        }
    }


    /**
     * 删除相关的多个job任务
     *
     * @param jobKeys
     */
    public static void removeJob(List<JobKey> jobKeys) {
        if(jobKeys == null || jobKeys.size() == 0) return;
        try {
            scheduler.deleteJobs(jobKeys); //删除任务后，所对应的trigger也将被删除
        } catch (Exception e) {
            throw new RuntimeException(e.getMessage());
        }
    }

}